package com.kavin.collection.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kavin.collection.annotation.ApiLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * TODO
 *
 * @author liuhaiquan
 * @date 2020/9/17
 * @description ${接口调用日志记录}
 */
@Slf4j
@Aspect
@Component
public class LogComponent {




    @Autowired
    private Environment environment;


    //应用名称key
    public static final String APPNAME_KEY = "spring.application.name";



    /* *
     * @Author liuhaiquan
     * @Description 基本类型Map
     * @Param
     * @return
     * @Date  2020/9/17 13:51
     **/
    private static HashMap<String, Class> map = new HashMap<String, Class>() {
        {
            put("java.lang.Integer", int.class);
            put("java.lang.Double", double.class);
            put("java.lang.Float", float.class);
            put("java.lang.Long", long.class);
            put("java.lang.Short", short.class);
            put("java.lang.Boolean", boolean.class);
            put("java.lang.Char", char.class);
            put("java.lang.String", String.class);
        }
    };


    /* *
     * @Author liuhaiquan
     * @Description 获取请求参数
     * @Param [joinPoint]
     * @return java.util.Map
     * @Date  2020/9/17 15:27
     **/
    private static Map<String,Object> getFieldsName(ProceedingJoinPoint joinPoint) throws Exception{
        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap<>();
        Object[] args = joinPoint.getArgs();
        if(Objects.isNull(args) || args.length == 0){
            return  paramMap;
        }
        String classType = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Class<?>[] classes = new Class[args.length];
        for (int k = 0; k < args.length; k++) {
            if (!args[k].getClass().isPrimitive()) {
                // 获取的是封装类型而不是基础类型
                String result = args[k].getClass().getName();
                Class s = map.get(result);
                classes[k] = s == null ? args[k].getClass() : s;
            }
        }
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        // 获取指定的方法，第二个参数可以不传，但是为了防止有重载的现象，还是需要传入参数的类型
        Method method = Class.forName(classType).getMethod(methodName, classes);
        // 参数名
        String[] parameterNames = pnd.getParameterNames(method);

        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }

    /**
     * 在方法执行之前、后执行该方法
     * 设置需要执行该方法的类路径和注解
     */
    @Around("execution(* com.kavin..*(..))  && (@annotation(com.kavin.collection.annotation.ApiLog))")
    public Object interceptor(ProceedingJoinPoint point) throws Throwable {
            log.info("调用接口切面记录日志开始=================================");
            String ip = "";
            Object result;
            Map<String, Object> paramMap;
            String businessIdKey = StringUtils.EMPTY;
            String service_name = point.getTarget().getClass().getName();
            String method_name = point.getSignature().getName();
            String app_name = environment.getProperty(APPNAME_KEY);
            String requestJson = StringUtils.EMPTY;
            String responseJson = StringUtils.EMPTY;
            try{
                paramMap = getFieldsName(point);
                requestJson = JSON.toJSONString(paramMap);
                log.info(method_name + ":请求参数" + ":" + requestJson);
                Signature signature = point.getSignature();
                MethodSignature methodSignature = (MethodSignature) signature;
                Method targetMethod = methodSignature.getMethod();
                //获取方法上注解中表明的权限
                RequestMapping reqAnnotation = targetMethod.getAnnotation(RequestMapping.class);//获取方法上的注解
                PostMapping postMapping = targetMethod.getAnnotation(PostMapping.class);//获取方法上的注解
                GetMapping getMapping = targetMethod.getAnnotation(GetMapping.class);//获取方法上的注解
                ApiLog apiLogAnnotation = targetMethod.getAnnotation(ApiLog.class);//获取方法上的注解
                if (Objects.nonNull(reqAnnotation) || Objects.nonNull(postMapping) || Objects.nonNull(getMapping)) { //Controller 层方法，获取地址
                    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                    ip = IpUtils.getRemoteAddr(attributes.getRequest());
                }
                List<String> businessKey = new ArrayList<>();
                if (Objects.nonNull(apiLogAnnotation)) { //获取业务Id
                    String[] businessId = apiLogAnnotation.businessId();
                    if (businessId.length > 0) {
                        Arrays.asList(businessId).forEach(item ->
                                businessKey.add(item)
                        );
                    }
                }
                businessIdKey = generateBusinessId(paramMap, businessKey);
                result = point.proceed(point.getArgs());
                responseJson = JSON.toJSONString(result);
                log.info(service_name+"." + method_name + "():响应信息" + ":" + responseJson);
            }finally {
/*                //TODO 插入日志表
                HyCallApiLog hyCallApiLog = new HyCallApiLog();
                hyCallApiLog.setIp(ip);
                hyCallApiLog.setAppName(app_name);
                hyCallApiLog.setServiceName(service_name);
                hyCallApiLog.setMethodName(method_name);
                hyCallApiLog.setRequestData(requestJson);
                hyCallApiLog.setBusinessId(businessIdKey);
                hyCallApiLog.setRequestTime(new Date());
                hyCallApiLog.setResponseData(responseJson);
                hyCallApiLog.setResponseTime(new Date());
                hyCallApiLogRepository.save(hyCallApiLog);*/
            }
            return result;
    }

    /* *
     * @Author liuhaiquan
     * @Description  从参数中找出BusinessId 需要的值
     * @Param [paramMap, businessKey]
     * @return java.lang.String
     * @Date  2020/9/18 11:05
     **/
    private String generateBusinessId(Map<String, Object> paramMap, List<String> businessKey)  {
        if(CollectionUtils.isEmpty(businessKey)) return "";
        StringBuffer stringBuffer = new StringBuffer();
        Map<String,String> resultMap = new HashMap<>();  //存储每个key 对应的值
        List<String> keys_temp = new ArrayList<>(businessKey);  //保证值的顺序和businessKey 的顺序是一致的
        for(Map.Entry<String,Object> entry : paramMap.entrySet()){
            Object obj = entry.getValue();
            String key = entry.getKey();
            if(businessKey.contains(key) && Objects.nonNull(map.get(obj.getClass().getName()))){   //参数直接匹配到businessKey了并且对应的value 是基本类型。直接取值
                resultMap.put(key,String.valueOf(obj));
                businessKey.remove(key); //第一次找到就不在查找这个key 对应的值了
            }else{  //从value中找businessKey.
                if(Objects.nonNull(map.get(obj.getClass().getName()))){   //如果是基本类型则直接继续下一次循环 （是基本类型且不是需要查找的key）
                    continue;
                }else if(obj instanceof Object){
                    JSONObject json=(JSONObject) JSONObject.toJSON(obj);
                    Iterator<String> iterator = businessKey.iterator();
                    while(iterator.hasNext()){
                        String queryKey = iterator.next();
                        TargetKey targetKey = TargetKey.builder().target(queryKey).build();
                        if(getValueFromJsonByKey(json,targetKey,false)){
                            resultMap.put(queryKey,String.valueOf(targetKey.getValue()));
                            iterator.remove();
                        }
                    }
                }else{
                    log.info("businessKey["+key+"]对应的数据类型暂不支持。");
                }
            }
            if(CollectionUtils.isEmpty(businessKey)){
                break;
            }
        }
        if(!org.springframework.util.CollectionUtils.isEmpty(resultMap)){    //保证businessKey的顺序
            Iterator<String> iterator = keys_temp.iterator();
            while(iterator.hasNext()){
                String key = iterator.next();
                stringBuffer.append(resultMap.get(key)).append(",");
            }
        }
        return stringBuffer.toString();
    }


    /* *
     * @Author liuhaiquan
     * @Description 获取对象的所有属性名
     * @Param [obj]
     * @return java.util.List<java.lang.String>
     * @Date  2020/9/18 11:33
     **/
    private List<String> getFieldNames(Object obj){
        List<String> result = new ArrayList<>();
        Field[] fields = obj.getClass().getDeclaredFields();
        Arrays.asList(fields).forEach(item -> result.add(item.getName()));
        return result;
    }


    /**
     * @Author kavin
     * @Description   从json 获取指定key对应的value
     * @Param [json, targetKey, result]
     * @return
     **/
    private Boolean getValueFromJsonByKey(JSONObject json,TargetKey targetKey ,Boolean result) {
        String target = targetKey.getTarget();
        if(!result) {
            for(String key :json.keySet()) {
                if(target.equals(key)) {
                    targetKey.setValue(json.get(key));
                    return true;
                }
                Object temp = json.get(key);
                if (temp instanceof JSONObject) {
                    JSONObject new_name = (JSONObject) temp;
                    result = getValueFromJsonByKey(new_name,targetKey, false);
                }
                if(result) {
                    return result ;
                }
            }
        }
        return result ;
    }







}
